﻿using System;
using System.Linq;
using System.IO.MemoryMappedFiles;
using System.Threading;
using System.Security.AccessControl;
namespace Collection
{
    internal enum Status { Loop = -1, Completed = -2 };
    public class RingBuffer : IDisposable
    {
        private bool _isDisposed;
        private Semaphore _semaphore;

        #region Data
        private byte[] _object;
        public byte[] Object
        {
            get { return _object; }
        }
        //stores maximum file size of ring buffer
        private int _bufferSize;
        //stores the buffer file Name
        private string _bufferName;
        //stores the semaphore Name
        private string _semaphoreName;
        //read pointer for data
        private int _readPtr;
        //write pointer for data
        private int _writePtr;
        //private readLoop number
        private int _readLoop;
        //private writeLoop number;
        private int _writeLoop;
        //File for storing data
        private MemoryMappedFile _dataFile;
        //view accessor for reading and writing data
        private MemoryMappedViewAccessor _dataAccessor;

        public bool WriteDataEnd()
        {
            while (true)
            {
                GetOffsets();
                GetLoops();
                //Am I in same loop && I can fit the object in space left
                if (this._writeLoop == this._readLoop && (_writePtr + sizeof(int)) > _bufferSize)
                {
                    if (_semaphore.WaitOne(0))
                    {
                        _dataAccessor.Write(this._writePtr, (int)Status.Loop);
                        //reset the write pointer
                        this._writePtr = 0;
                        //Update the offset
                        SetWriteOffsets();
                        //increment the loop number
                        this._writeLoop++;
                        //tell that we are moving in next loop
                        SetWriteLoops();
                        //free the semaphore
                        _semaphore.Release();
                    }
                    //Thread.Sleep(1);
                }
                else if ((this._writePtr + sizeof(int)) > this._readPtr && this._readLoop < this._writeLoop) //Am I in next loop
                {
                    //Thread.Sleep(1);
                }
                else
                {
                    if (_semaphore.WaitOne(0))
                    {
                        //terminate the loop and read the object
                        break;
                    }
                    //Thread.Sleep(1);
                }

            }
            //write the end of loop
            _dataAccessor.Write(_writePtr, (int)Status.Completed);
            this._writePtr = this._writePtr + sizeof(int);
            SetWriteOffsets();
            _semaphore.Release();
            return true;
        }

        //write object array
        public bool WriteData(byte[] _object)
        {
            int _objectSize = _object.Count();
            while (true)
            {
                GetOffsets();
                GetLoops();
                //Am I in same loop && I can fit the object in space left
                if (this._writeLoop == this._readLoop && (_writePtr + _objectSize + sizeof(int)) > _bufferSize)
                {
                    if (_semaphore.WaitOne(0))
                    {
                        _dataAccessor.Write(this._writePtr, (int)Status.Loop);
                        //reset the write pointer
                        this._writePtr = 0;
                        //Update the offset
                        SetWriteOffsets();
                        //increment the loop number
                        this._writeLoop++;
                        //tell that we are moving in next loop
                        SetWriteLoops();
                        //free the semaphore
                        _semaphore.Release();
                    }
                    //Thread.Sleep(1);
                }
                else if ((this._writePtr + _objectSize + sizeof(int)) > this._readPtr && this._readLoop < this._writeLoop) //Am I in next loop
                {
                    continue;
                    //Thread.Sleep(1);
                }
                else
                {
                    if (_semaphore.WaitOne(0))
                    {
                        //terminate the loop and read the object
                        break;
                    }
                    //Thread.Sleep(1);
                }

            }
            _dataAccessor.Write(_writePtr, _objectSize);
            //increment the next write position by size of int
            this._writePtr = this._writePtr + sizeof(int);
            //write the data payload
            _dataAccessor.WriteArray(_writePtr, _object, 0, _objectSize);
            //update the next write position by increasing the number of bytes
            this._writePtr = _writePtr + _objectSize;
            //write the write pointer offset
            SetWriteOffsets();
            _semaphore.Release();
            return true;
        }

        //read data from queue
        public bool ReadData()
        {
            int _objectSize;
            while (true)
            {
                GetOffsets();
                GetLoops();
                //If I am with the writer then wait
                while (this._readPtr == this._writePtr && this._readLoop == this._writeLoop)
                {
                    //waiting for writer to start
                    //Task.WaitAll(Task.Delay(1));
                    //recheck the writer
                    GetWriteOffsets();
                    GetWriteLoops();
                }
                //read the offset
                _dataAccessor.Read(this._readPtr, out _objectSize);
                if (_objectSize == (int)Status.Completed)
                {
                    return false;
                }//Do I need to loop?
                else if (_objectSize == (int)Status.Loop)
                {
                    if (_semaphore.WaitOne(0))
                    {
                        //reset the read pointer
                        this._readPtr = 0;
                        //Mark the read pointer
                        SetReadOffsets();
                        //increment the loop number
                        this._readLoop++;
                        //Save the offsets
                        SetReadLoops();
                        _semaphore.Release();
                    }
                }
                else
                {
                    //check if 
                    if (_semaphore.WaitOne(0))
                    {
                        //terminate the loop and read the object
                        break;
                    }
                }
            }

            //read the object size
            _dataAccessor.Read(this._readPtr, out _objectSize);
            //move the read pointer to data location
            this._readPtr = this._readPtr + sizeof(int);
            //initialize the data array
            _object = new byte[_objectSize];
            //read the data object and populate the array
            _dataAccessor.ReadArray(this._readPtr, _object, 0, _objectSize);
            //increment the reader pointer location to new location
            this._readPtr = this._readPtr + _objectSize;
            //save the read offsets
            SetReadOffsets();
            //signal another thread to continue
            _semaphore.Release();
            return true;
        }

        #endregion Data

        #region Journal
        //stores the journal file Name
        private string _journalName;
        //File for storing data
        private MemoryMappedFile _journalFile;
        //view accessor for reading and writing journal data
        private MemoryMappedViewAccessor _journalAccessor;

        private void GetLoops()
        {
            GetWriteLoops();
            GetReadLoops();
        }
        //get the read offset
        private void GetReadLoops()
        {
            this._readLoop = _journalAccessor.ReadInt32(2 * sizeof(int));
        }
        //get the write offset
        private void GetWriteLoops()
        {
            this._writeLoop = _journalAccessor.ReadInt32(3 * sizeof(int));
        }
        //store the writers pointer value
        private void SetWriteLoops()
        {
            _journalAccessor.Write(3 * sizeof(int), this._writeLoop);
            _journalAccessor.Flush();
        }

        //store the readers pointer value
        private void SetReadLoops()
        {
            _journalAccessor.Write(2 * sizeof(int), this._readLoop);
            _journalAccessor.Flush();
        }
        //get reader status
        private void GetOffsets()
        {
            GetWriteOffsets();
            GetReadOffsets();
        }
        //get the read offset
        private void GetReadOffsets()
        {
            this._readPtr = _journalAccessor.ReadInt32(0);
        }
        //get the write offset
        private void GetWriteOffsets()
        {
            this._writePtr = _journalAccessor.ReadInt32(sizeof(int));
        }
        //store the writers pointer value
        private void SetWriteOffsets()
        {
            _journalAccessor.Write(sizeof(int), this._writePtr);
            _journalAccessor.Flush();
        }
        //store the readers pointer value
        private void SetReadOffsets()
        {
            _journalAccessor.Write(0, this._readPtr);
            _journalAccessor.Flush();
        }

        #endregion Journal

        public RingBuffer(string _bufferName, int _bufferSize)
        {
            this._bufferName = @"Global\" + _bufferName + ".bin";
            this._journalName = @"Global\" + "Journal_" + _bufferName + ".bin";
            this._semaphoreName = @"Global\" + "Journal_" + _bufferName + ".sp";
            this._bufferSize = _bufferSize;
            this._isDisposed = false;
            this._readPtr = 0;
            this._writePtr = 0;
            this._readLoop = 0;
            this._writeLoop = 0;
        }

        public void InitializeMemory()
        {
            bool _create;
            try
            {
                SemaphoreSecurity semSec = new SemaphoreSecurity();
                SemaphoreAccessRule rule = new SemaphoreAccessRule("Everyone", SemaphoreRights.FullControl, AccessControlType.Allow);
                semSec.AddAccessRule(rule);
                _semaphore = new Semaphore(1, 1, this._semaphoreName, out _create, semSec);
            }
            catch
            {
                _semaphore = Semaphore.OpenExisting(this._semaphoreName);
            }
            _semaphore.WaitOne();
            try
            {
                MemoryMappedFileSecurity mmfSecurity = new MemoryMappedFileSecurity();
                MemoryMappedFileSecurity mmfSecurity1 = new MemoryMappedFileSecurity();
                mmfSecurity.AddAccessRule(new AccessRule<MemoryMappedFileRights>("Everyone", MemoryMappedFileRights.FullControl, AccessControlType.Allow));
                mmfSecurity1.AddAccessRule(new AccessRule<MemoryMappedFileRights>("Everyone", MemoryMappedFileRights.FullControl, AccessControlType.Allow));

                _dataFile = MemoryMappedFile.CreateNew(_bufferName, _bufferSize, MemoryMappedFileAccess.ReadWriteExecute, MemoryMappedFileOptions.None, mmfSecurity, System.IO.HandleInheritability.Inheritable);
                _journalFile = MemoryMappedFile.CreateNew(_journalName, 1024, MemoryMappedFileAccess.ReadWriteExecute, MemoryMappedFileOptions.None, mmfSecurity1, System.IO.HandleInheritability.Inheritable);
                _journalAccessor = _journalFile.CreateViewAccessor();
                _dataAccessor = _dataFile.CreateViewAccessor();

                _journalAccessor.Write(0, this._readPtr);
                _journalAccessor.Write(1 * sizeof(int), this._writePtr);
                _journalAccessor.Write(2 * sizeof(int), this._readLoop);
                _journalAccessor.Write(3 * sizeof(int), this._writeLoop);

            }
            catch
            {
                _dataFile = MemoryMappedFile.OpenExisting(_bufferName, MemoryMappedFileRights.ReadWriteExecute);
                _journalFile = MemoryMappedFile.OpenExisting(_journalName, MemoryMappedFileRights.ReadWriteExecute);
                _dataAccessor = _dataFile.CreateViewAccessor();
                _journalAccessor = _journalFile.CreateViewAccessor();
                GetOffsets();
                GetLoops();
            }
            _semaphore.Release();

        }

        //close the connections and dispose the objects
        public void Dispose()
        {

            if (_dataAccessor != null)
            {
                _dataAccessor.Dispose();
                _dataAccessor = null;
            }
            if (_journalAccessor != null)
            {
                _journalAccessor.Dispose();
                _journalAccessor = null;
            }
            if (_dataFile != null)
            {
                _dataFile.Dispose();
                _dataFile = null;
            }
            if (_journalFile != null)
            {
                _journalFile.Dispose();
                _journalFile = null;
            }
            if (_semaphore != null)
            {
                _semaphore.Dispose();
                _semaphore = null;
            }
            this._bufferName = null;
            this._journalName = null;
            this._object = null;
            this._isDisposed = true;

        }

        ~RingBuffer()
        {
            if (!_isDisposed)
            {
                this.Dispose();
            }

        }
    }
}
